通过四篇经典论文,大二学弟学GAN是这么干的 您所在的位置:网站首页 github 太慢 通过四篇经典论文,大二学弟学GAN是这么干的

通过四篇经典论文,大二学弟学GAN是这么干的

#通过四篇经典论文,大二学弟学GAN是这么干的| 来源: 网络整理| 查看: 265

0 分享至

用微信扫码二维码

分享至好友和朋友圈

深圳福田部分社区开始全员核酸检测,希望大家积极响应。(采样只需数秒)

【飞桨开发者说】李宇奇,金陵科技学院本科在读。爱好领域:计算机视觉、对抗网络。

最近在AI Studio上学习李宏毅老师的强化学习课程时,老师提及了GAN这个概念。老师说GAN的思想与强化学习很像,李宏毅老师还发表了一篇StepGAN,将强化学习中的Q-learning与GAN结合了起来,很有意思。

经了解,GAN是一种很炫酷的技术,使用它,可以进行AI换脸,风格迁移,对文本生成进行优化,甚至可以看看将三维的我们展开为二维的纸片人的形象。

下面让我们开始GAN的学习吧~

Traditional GAN

1 GAN的原理

我们先学习一下传统的GAN,也就是我们无敌的Goodfellow大神的那篇开山之作Generative Adversarial Nets。首先看论文的标题,Generative 表明我们这次玩的是生成模型。目前深度学习大部分任务都是鉴别模型,这主要归功于反向传播算法与网络的加深。生成模型一直是学界的一个难题,第一大原因:在最大似然估计和相关策略中出现许多难以处理的概率计算,生成模型难以逼近。第二大原因:生成模型难以在生成环境中利用分段线性单元的好处,因此其影响较小。再看看后面的Adversarial和Nets,我们注意到是Nets而不是Net,说明这里应该有多个网络,并且它们的关系是Adversarial,相互对抗的。看到这里,我们大概对本篇论文有了大致的了解,下面让我们看看GAN的原理。

GAN开创性地提出了一种对抗关系,设计了两个网络,一个鉴别器(Discriminator),一个生成器(Generator)。为了让生成器学会训练集上的数据x的概率分布Px,我们定义了一个关于输入噪声变量Pz(z)的先验分布。通过生成器映射为G(z;θg),使其与训练集图片的像素概率分布Px尽可能接近。鉴别器则接收一个像素矩阵(可能来自训练集,也可能来自生成器),输出一个标量,代表输入是来自训练集而不是G(z;θg)的概率。我们训练鉴别器D以最大限度地提升为训练样本和来自G的样本分配正确标签的概率。同时训练生成器G以最小化对数(1−D(G(z)))。换句话说,D和G玩以下具有值函数V(G、D)的双人极大最小游戏。

下图展示了GAN的训练过程:

相关说明:

蓝色虚线为Discriminator的鉴别分布

黑色虚线为训练集的数据分布

绿色实线为Generator的生成分布

向上箭头显示映射x=G(z)如何将非均匀分布Pg施加在转换样本上

流程介绍:

(a) D和G都是随机初始化后的分布

(b) D经历了迭代,对于接近训练集的数据予以高分,对Pg分布予以低分

(c) G也经历了迭代,D的梯度已经引导Pg流向更有可能被归类为训练集的区域

(d) D和G经历了很多次迭代,Pg已经完全拟合了训练集的分布,而D也无法分辨出Pg与Px的区别,统统给了0.5分(注意,这是非常理想的情况,实作中可能达不到)

2 实操

下面,我们将分别介绍网络架构、模型训练。

2.1 ConvBN层的定义

下面程序中,ConvBN类包含了基础的二维卷积、批标准化和激活函数层,三者构成卷积网络的基本单元。有划水员可能发现笔者在work下的ConvBN里定义了mish激活,笔者本来想用mish的,但是发现效果一般,并且消耗计算资源比较多,就放弃了。但是mish的曲线确实丝滑,神经元活性十足,大家可以尝试一下。

import paddle.nn as nnimport paddleclass ConvBN(nn.Layer):def __init__(self,num_channels,num_filters,filter_size,stride=1,padding="valid"):super(ConvBN, self).__init__()#nn.initializer.set_global_initializer(nn.initializer.KaimingUniform(),nn.initializer.Constant(0.1))self.conv=nn.Conv2D(num_channels,num_filters,filter_size,stride,padding=padding,bias_attr=False)self.BN=nn.BatchNorm(num_filters)self.lrelu=nn.LeakyReLU(0.2)

def mish(self,x):return x*paddle.tanh(paddle.log(paddle.exp(x)+1))

def forward(self,x):x=self.conv(x)x=self.BN(x)x=self.lrelu(x)return x

下面我们分别介绍生成器与鉴别器的架构:

2.2 生成器

生成器Tips:

笔者经过多次实验,发现生成器的卷积要尽可能大一些。另外,每一层都加一层Batchnorm,除了输出层(DCGAN论文里是这么说的,笔者经过实作发现确实如此。如果在输出层加了Batchnorm,收敛会不稳定,同时比较慢)。

尽量不用反卷积,少用转置卷积,否则很容易出现棋盘效应,图像的颗粒感很严重。

解决方法:知乎大神说,可以使用上采样加多层卷积(upsample+conv) / (pixelshuffle+conv),后者听说效果更好,在超分辨率里也有使用。

生成器激活函数使用ReLU,DCGAN原文中如是说。

%matplotlib inline #调用魔术方法,显示matplotlib的图像import matplotlib.pyplot as plt#从work目录中导入ConvBN类from work.ConvBN import ConvBNimport paddle.nn as nn#将warning干掉import warningswarnings.filterwarnings("ignore", category=Warning)

class Generator(nn.Layer):def __init__(self):super(Generator, self).__init__()#nn.initializer.set_global_initializer(nn.initializer.KaimingUniform(), nn.initializer.Constant(0.1))model=[#padding方式设为same,即不会改变图像的形状,当然步长要设为1ConvBN(1,128,5,1,padding="same"),#将图像的通道数增大4**2倍,同时将大小减2至8ConvBN(128,128*16,3,1),#上采样,将大小扩大4倍,同时将通道数缩小4**2倍nn.PixelShuffle(4),ConvBN(128,128,5,1,padding="same"),#最后一层去掉Batchnormnn.Conv2D(128,1,5,1,padding="same"),#将RGB值限制在0到1,也同样可以读取图像色彩nn.Sigmoid()]self.model=nn.Sequential(*model)

def forward(self,x):#将数据展开x=paddle.reshape(x,[batch_size,1,10,10])return self.model(x)

2.3 鉴别器

鉴别器Tips:

不同的初始化经过实验发现差别很大,但只是在随机生成任务中(低维度随机生成的向量映射到高维图像)差别很大,在其它的GAN任务中差别不大。总之,笔者这里借用了百度官方的初始化方式。

生成器与鉴别器的卷积层数量最好一样,本质上是让两个神经网络的参数量相同,即复杂度相同。

生成器激活函数使用Leaky ReLU,DCGAN原文中如是说,参数设为0.2.

输出层使用Sigmoid激活,使图像的分数限制在0到1.

#从work目录导入ConvBN类from work.ConvBN import ConvBNclass Discriminator(nn.Layer):def __init__(self):super(Discriminator, self).__init__()#nn.initializer.set_global_initializer(nn.initializer.KaimingUniform(), nn.initializer.Constant(0.1))model=[#将通道数扩大至64,学习更多特征,同时卷积核大小设为5,感知野更大ConvBN(1,64,5,2),#增大通道数,进一步缩小图像大小ConvBN(64,128,5,2),#缩小图像大小,通道数不变,因为是手写数字,特征已经够多了ConvBN(128,128,5,2),#卷积核大小为1,步长为1,单纯合并通道nn.Conv2D(128,1,1,1),#将数据展开nn.Flatten(),#将分数限制在0到1nn.Sigmoid()]self.model=nn.Sequential(*model)

def forward(self,x):x=self.model(x)return x

2.4 模型训练

2.4.1 迭代算法

For 每轮 doFor k次(k是超参,即训练鉴别器k次,再训练生成器1次) do从噪声分布中取出m笔数据z_i从数据集中取出m笔数据x_i使用梯度上升更新迭代器:最大化log(D(x_i))+log(1-D(G(z_i)))执行一次从噪声分布中取出m笔数据z_i使用梯度下降更新生成器:最小化log(1-D(G(z_i)))

训练Tips:

生成器和鉴别器的优化器分开定义,因为梯度反向传播时要防止互相影响。

要先迭代生成器k次,再迭代鉴别器,此处k是超参,需要自己调,炼丹当然要自己炼。

注意上述算法中对鉴别器的迭代需要梯度上升(gradient ascent),而对生成器是梯度下降。

笔者实作时发现极其容易梯度爆炸,经过两个星期的研究,发现是log与鉴别器的sigmoid产生了矛盾,刚开始迭代时鉴别器是随机初始化,很容易给出0分,而当输入为0左右时,log趋向与负无穷,所以梯度爆炸。强烈建议把log去掉。

2.4.2 实操训练

#设置训练轮数epoch_num = 100#开启visualdlfrom visualdl import LogWriterimport warnings#此处可以把warning干掉warnings.filterwarnings("ignore", category=Warning)log_writer = LogWriter("./log/vdl_log")

#设置学习率,可以把生成器和鉴别器的学习率分开设置,让鉴别器学的更快g_learning_rate = 2e-4d_learning_rate = 2e-4k = 3 #鉴别器迭代k轮,生成器迭代1轮g_clip = paddle.nn.ClipGradByNorm(clip_norm=1e-2) #梯度裁剪,防止梯度爆炸,但后续发现加了梯度裁剪也会使收敛变慢,d_clip = paddle.nn.ClipGradByNorm(clip_norm=1e-2) #经过尝试发现去掉梯度裁剪在traditional GAN中不会梯度爆炸。。。#去掉注释即开启梯度裁剪#g_optimizer = paddle.optimizer.Adam(learning_rate=g_learning_rate, parameters=generator.parameters(),#beta1=0.5, beta2=0.999,grad_clip=g_clip)#定义优化器,此处的超参都是根据DCGAN论文设置,DCGAN这篇论文确实讲的很细g_optimizer = paddle.optimizer.Adam(learning_rate=g_learning_rate, parameters=generator.parameters(),beta1=0.5, beta2=0.999)d_optimizer = paddle.optimizer.Adam(learning_rate=d_learning_rate, parameters=discriminator.parameters(),beta1=0.5, beta2=0.999)#这里使用交叉熵损失,用来衡量预测值与标签的差距loss=nn.BCELoss()with log_writer as logger:#step_d,step_g记录每一次迭代后的损失值step_d, step_g = 0, 0#将网络的模式设置为训练模式generator.train()discriminator.train()for epoch in range(epoch_num):for i, x in enumerate(dataloader()):#预先清除梯度,防止对迭代造成影响d_optimizer.clear_grad()#取出图像数据real = x[0]#获取噪声noise = paddle.randn([batch_size, 100])#得到虚假图像fake = generator(noise)#获取真实图像与噪声的分数fake_score = discriminator(fake)real_score = discriminator(real)#真实图像的满分标签ones=paddle.ones([batch_size,1])#得到真实图像的分数与标签的损失值real_d_loss=loss(real_score,ones)real_d_loss.backward() #这里不需要optimize.step(),梯度自动累计,在下面再进行更新

#噪声的零分标签zeros=paddle.zeros([batch_size,1])#计算虚假图像的损失值fake_d_loss=loss(fake_score,zeros)#反向传播梯度fake_d_loss.backward()#更新网络梯度d_optimizer.step()#清除梯度d_optimizer.clear_grad()#对d_loss求和d_loss=fake_d_loss+real_d_loss#记录至log中logger.add_scalar(tag="d_loss", step=step_d, value=d_loss)#记录鉴别器迭代次数step_d+=1

if i % k == 0:#预先清除梯度g_optimizer.clear_grad()#使用正态分布获取噪声noise = paddle.randn([batch_size, 100])#得到虚假图像fake = generator(noise)#计算虚假图像的分数fake_score = discriminator(fake)#这里因为是训练生成器,目标是最大化噪声的分数,所以这里给出满分标签ones=paddle.ones([batch_size,1])#交叉熵损失衡量分数与标签的差距g_loss=loss(fake_score,ones)#使用链式法则反向传播计算梯度g_loss.backward()#最小化g_lossg_optimizer.minimize(g_loss)#清楚梯度g_optimizer.clear_grad()#将g_loss值添加入log中logger.add_scalar(tag="g_loss", step=step_g, value=g_loss)#记录生成器迭代次数step_g += 1if (i + 1) % 100 == 0:print("epoch:%d,i:%d,d_loss:%f,g_loss:%f,fake_score:%f,real_score:%f" % (epoch, i, d_loss, g_loss, paddle.mean(fake_score), paddle.mean(real_score)))

#图形化,看看当前生成器的水平noise = paddle.randn([batch_size, 100])#获取虚假图像fake = generator(noise)#将tensor转化为numpygenerated_image = fake.numpy()#设置框的大小plt.figure(figsize=(15,15))try:for i in range(10):#取出图像image = generated_image[i].transpose()#获取像素值大于0的RGB值,小于0的地方填0image = np.where(image > 0, image, 0)#改变image的形状image = image.transpose((1,0,2))#设置子图的横纵轴与位置plt.subplot(10, 10, i + 1)#展示图像plt.imshow(image[...,0], vmin=-1, vmax=1)#关闭子图的轴plt.axis('off')#改变横轴plt.xticks([])#改变纵轴plt.yticks([])#微调坐标轴形状plt.subplots_adjust(wspace=0.1, hspace=0.1)#设置标题msg = 'Epoch ID={0} Batch ID={1} \n\n D-Loss={2} G-Loss={3}'.format(epoch, i, d_loss.numpy(), g_loss.numpy())print(msg)plt.suptitle(msg,fontsize=20)#重新绘制图像,适用于绘制子图时plt.draw()#保存图像plt.savefig('{}/{:04d}_{:04d}.png'.format('work', pass_id, batch_id), bbox_inches='tight')#循环时保证图像不消失plt.pause(0.01)except IOError:print(IOError)

if (epoch + 1) % 10 == 0:#保存模型参数paddle.save(generator.state_dict(), "work/generator.pdparams")paddle.save(discriminator.state_dict(), "work/discriminator.pdparams")

‍‍‍‍‍‍‍‍‍‍‍3 Traditional GAN的优缺点

缺点:

没有Pg(x)(即G的数据空间)的明确表示,随机性比较大。

D在训练期间必须与G同步(特别是在不更新D的情况下不能训练太多,以避免G将太多的随机分布映射到相同的图片,使G的数据空间具备多样性)。

优点:

不需要马尔可夫链,只使用反向支撑来获得梯度。

在学习过程中不需要推理,并且在模型中可以纳入各种函数(变化很多,有很大的提升空间)。

LSGAN

1 Abstract(摘要)

众所周知,Traditional GAN真的是极其难train,动不动就梯度消失或者爆炸。另外,生成的图像质量也不咋滴。因此,LSGAN横空出世,缓解了上述两个问题。不说废话,直接讲原理和实作~

2 Algorithm(算法)

LSGAN的迭代算法如上图所示,与Traditional GAN有较大不同,将原先的F散度改成了最小二乘损失函数。原先最小化GAN的目标函数会出现梯度的消失,这使得很难更新生成器。LSGAN可以缓解这个问题,因为LSGANs根据样本到决策边界的距离来惩罚样本,从而产生更多的梯度来更新生成器。另外,与常规GAN几乎不会丢失决策边界正确一侧的样本不同,即使样本正确分类,LSGAN也会对其进行惩罚,有效缓解了梯度消失的问题。

3 Implement(实操)

理论一大堆,其实实操很简单,就是将交叉熵换成最小二乘。

loss=nn.BCELoss()->loss=nn.MSELoss()

BCELoss就是binary cross entropy损失,使用交叉熵衡量预测值与标签的差距

MSELoss就是mean square error损失,使用均方误差衡量预测值与标签差距

Tips:

上述的算法有两种实现方法

这里要将鉴别器的输出层改成Tanh激活,使输出限制在-1到1

这里跟traditional GAN差不多,网络代码不用改。

WGAN与WGAN-GP

1 Abstract(摘要)

经历了让人心碎的Traditional GAN的迭代算法后,我们又学习了LSGAN,用来稳定GAN的训练与提升图像的质量。最后,让我们来学习无敌的WGAN-GP,虽然有难度……依然不讲废话,直接讲原理和算法,吼吼~

WGAN提出了一种衡量模型预测的样本与真实样本的距离,极其复杂。原文称为Earth Mover,也叫推土机距离。一开始作者自己也不知道怎么实作,就简单加了个grad clipping,当然有效……后来,作者终于肝出来了,也就是我们的WGAN-GP,下面介绍算法与实作。

2 Algorithm(算法)

当生成器还没有收敛时,do迭代k次(k是超参,即训练鉴别器k次,再训练生成器1次)从数据集中取出数据x,噪声z,一个0至1的随机数mx_tita=G(z)x_hat=m*x+(1-m)*x_titaloss=D(x_tita)-D(x)+lamt*(D(x_hat)的梯度的第二范数-1)**2对生成器进行梯度下降迭代执行一次:获取噪声z对-D(G(Z))进行梯度下降迭代

3 Implement(实操)

#此处实现上图中的获取梯度惩罚项def gradient_penalty(discriminator, real, fake, batchsize,lamt):#real是真实图像#fake是虚假图像t = paddle.uniform((batchsize,1,1,1))#扩大形状t = paddle.expand_as(t, real)#对真实图像与虚假图像取噪声求均值inter = t * real + (1-t) * fakeinter.stop_gradient = Falseinter_ = discriminator(inter)#调用Paddle的API求导grads = paddle.grad(inter_, [inter])[0]epsilon = 1e-12#获取normnorm = paddle.sqrt(paddle.mean(paddle.square(grads), axis=1) + epsilon)#lamt是倍率gp = paddle.mean((norm - 1)**2)*lamt

return gp

以下是调用示范:

gp=gradient_penalty(discriminator,real,fake,10)d_loss=paddle.mean(fake_score)-paddle.mean(real_score)+gpd_loss.backward()d_optimize.step()g_loss=-paddle.mean(fake_score)

以上代码来自叶月火狐大佬~

项目链接:

https://aistudio.baidu.com/aistudio/projectdetail/1426558

Tips:

对g_loss取负号,即将梯度下降转化为梯度上升。

使用WGAN时,需要将鉴别器的输出层Sigmoid拔掉,使输出变成线性的结果,防止当鉴别器饱和时的梯度消失。

将优化器改成Adam

结果展示

从总体来说,生成了较为清晰的图片,但数字略有模糊,说明我们的生成器的水平还有待提高。

总结与展望

通过使用飞桨框架2.0版本,我们完美完成了四篇GAN论文的复现,并且体验极其舒适,也达到了原论文的表现。

希望飞桨继续加速迭代,更好地支持最新技术,简化开发流程,受益大众。同时,也希望开源社区的伙伴们共同开发更多的算子,提升训练速度,让更多的模型可以在口袋里运行,造福百姓!

项目链接

https://aistudio.baidu.com/aistudio/projectdetail/1668279

如果您想详细了解更多飞桨的相关内容,请参阅以下文档。

·飞桨官网地址·

https://www.paddlepaddle.org.cn/

·飞桨开源框架项目地址·

GitHub: https://github.com/PaddlePaddle/Paddle

Gitee: https://gitee.com/paddlepaddle/Paddle

关注送好礼

我们的视频号来啦!

关注下方视频号“开开开源”并点赞

6月10日14:00

将从关注者中随机抽取10位幸运用户

送出精美礼品一份哦!

如有疑问,可添加微信“zls20201123”咨询

许式伟:Go+门槛比Go低,小孩6年级可开始学Go+

2021-06-05

一周暴涨1300+ Star,一款相当牛的超实用工具

2021-06-04

开源囧事|捅娄子了,写个bug被国家信息安全漏洞共享平台抓到了?

2021-06-06

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

/阅读下一篇/ 返回网易首页 下载网易新闻客户端


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有